using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text.RegularExpressions;

namespace Irc.Objects
{
	[CLSCompliant(true)]
	public sealed class Nickname : Target, IComparable, IFormattable
	{
		private ModeCollection accessLevel;
		private string username;
		private string hostmask;
		private string realname;
		private string name;

		public ModeCollection AccessLevel {
			get { return accessLevel; }
		}
		public string Name
		{
			internal set { name = value; }
			get { return name; }
		}
		public string Realname
		{
			internal set { realname = value; }
			get { return realname; }
		}
		public string Hostmask
		{
			internal set { hostmask = value; }
			get { return hostmask; }
		}
		public string Username
		{
			internal set { username = value; }
			get { return username; }
		}
		public Nickname(string nick, string real, string host, string user, params ModeName[] access)
		{
			Name = nick;
			Realname = real;
			Hostmask = host;
			Username = user;
			accessLevel = new ModeCollection();
			if(access != null)
				foreach(ModeName m in access)
					accessLevel += new Mode(this, m);
		}

		public Nickname(string nick) : this(nick, "*", "*", "", ModeName.None) {}
		public Nickname(string nick, string host) : this(nick, "*", host, "", ModeName.None) { }
		public Nickname(string nick, string real, string host) : this(nick, real, host, "", ModeName.None) { }
		public Nickname(string nick, string real, string host, string user) : this(nick, real, host, user, ModeName.None) { }
		
		public Nickname(string nick, ModeName access) : this(nick, "*", "*", "", access) { }
		public Nickname(string nick, string host, ModeName access) : this(nick, "*", host, "", access) { }
		public Nickname(string nick, string real, string host, ModeName access) : this(nick, real, host, "", access) { }
		
		public Nickname(string nick, ModeName[] access) : this(nick, "*", "*", "", access) { }
		public Nickname(string nick, string host, ModeName[] access) : this(nick, "*", host, "", access) { }
		
		public static Nickname Parse(string format)
		{
			Nickname result;
			Regex r = new Regex(@"(?<nick>[^!]+)(!(?<real>[^@]+)@(?<host>.*))?", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
			Match m = r.Match(format);
			if(!m.Success)
				throw new FormatException();
			if(m.Groups["real"].Success)
				result = new Nickname(m.Groups["nick"].Value, m.Groups["real"].Value, m.Groups["host"].Value);
			else
				result = new Nickname(m.Groups["nick"].Value);
			return result;
		}
		public static bool TryParse(string format, out Nickname result)
		{
			try {
				result = Parse(format);
				return true;
			} catch(FormatException) {
				result = null;
				return false;
			}
		}
		public int CompareTo(object obj)
		{
			Nickname n = obj as Nickname;
			if(n != null)
			{
				// if the Name properties of each nickname match, the ignore checking entirely
				if(n.Name.Equals(Name))
					return 0;
				string theirHostmask = n.ToString("f", System.Globalization.CultureInfo.CurrentCulture);
				string ourHostmask = ToString("f", System.Globalization.CultureInfo.CurrentCulture);
				for(int i = 0, j = 0; i < theirHostmask.Length && j < ourHostmask.Length; )
				{
					if(ourHostmask[i] != theirHostmask[j])
					{
						bool isHandled = false;
						if(ourHostmask[i] == '*' || theirHostmask[j] == '*' || ourHostmask[i] == '?' || theirHostmask[j] == '?' || ourHostmask[i] == '~' || theirHostmask[j] == '~')
							isHandled = true;
						if(ourHostmask[i] == '*' && ((i+1 < ourHostmask.Length && ourHostmask[i+1] != theirHostmask[j]) || (i+1 == ourHostmask.Length)))
							j++;
						else if(theirHostmask[j] == '*' && ((j+1 < theirHostmask.Length && theirHostmask[j+1] != ourHostmask[i]) || (j+1 == theirHostmask.Length)))
							i++;
						else if(ourHostmask[i] == '~')
							i++;
						else if(theirHostmask[j] == '~')
							j++;
						if(!isHandled)
							return (ourHostmask[i] - theirHostmask[j]);
					} else
					{
						i++;
						j++;
					}
				}
				return 0;
			} else
				throw new ArgumentException(Strings.NicknameCastException);
		}
		public override bool Equals(object obj)
		{
			if(obj != null)
			{
				Nickname n = obj as Nickname;
				try
				{
					return CompareTo(n) == 0;
				}
				catch(ArgumentException)
				{
					return false;
				}
			} else
				return false;
		}
		public override int GetHashCode()
		{
			return base.GetHashCode();
		}
		public override string ToString()
		{
			return ToString("G", System.Globalization.CultureInfo.CurrentCulture);
		}
		public string ToString(string format)
		{
			return ToString(format, System.Globalization.CultureInfo.CurrentCulture);
		}
		public string ToString(string format, IFormatProvider formatProvider)
		{
			switch(format)
			{
				case "f":
					return Name+"!"+Realname+"@"+Hostmask;
				case "G":case "g":case "n":case null:
					return Name;
				case "S":case "s":
					return AccessLevel.ToString("S", System.Globalization.CultureInfo.CurrentCulture) + Name;
				default:
					throw new FormatException(String.Format(formatProvider, Strings.NicknameFormatException, format));
			}
		}
		public static bool operator ==(Nickname obj1, Nickname obj2)
		{
			// prevent infinite recursion
			if(((object)obj1) == null || ((object)obj2) == null)
				if(((object)obj1) == null && ((object)obj2) == null)
					return true;
				else
					return false;
			if(((object)obj1) != null)
				return obj1.Equals(obj2);
			else return ((object)obj2) == null;
		}
		public static bool operator !=(Nickname obj1, Nickname obj2)
		{
			return !(obj1 == obj2);
		}
		public static int operator >(Nickname obj1, Nickname obj2)
		{
			if(obj1 != null)
				return obj1.CompareTo(obj2);
			else return 1;
		}
		public static int operator <(Nickname obj1, Nickname obj2)
		{
			if(obj2 != null)
				return obj2.CompareTo(obj1);
			else return 1;
		}
		public static implicit operator string(Nickname nick)
		{
			if(nick != null)
				return nick.ToString();
			else return String.Empty;
		}
	}
}
